package ch.ethz.fcl.metrobuzz.render.od.map_view;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.MBContainer;
import ch.ethz.fcl.metrobuzz.data.od.SelectLinkQuery;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Trip;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Trip.TripSegment;
import ch.ethz.fcl.metrobuzz.render.od.flow_view.ODFrame;
import ch.ethz.fcl.metrobuzz.render.techniques.arrow.Arrow;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.SGMRTColorTable;
import ch.ethz.fcl.metrobuzz.render.techniques.projection.Projection;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Polygon;
import ch.ethz.fcl.metrobuzz.spatialindex.object3d.Point3D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;
import ch.ethz.fcl.metrobuzz.viewer.MBViewer;
import ch.ethz.fcl.metrofont.MetroFont;
import ch.ethz.fcl.metrofont.Pen;

public class MapViewer {
	private MBContainer container = null;

	ODFrame odSankey = null;

	SelectLinkQuery selectLinkQuery = null;

	Map<Edge, Polygon> edgePolygonMap = null;

	LinkSelection actionListener;

	public final static float MAX_WIDTH = 0.02F;

	public final static float THOURSAND_RADIUS = 0.01f;

	private float minRadius = 0;

	public static int count = 0;

	public static List<Node> highlightNodeList;

	public MapViewer(MBContainer container) {
		this.container = container;

		selectLinkQuery = new SelectLinkQuery(container.getData());

		actionListener = new LinkSelection(container, this);
		odSankey = new ODFrame(selectLinkQuery);
	}

	public void handleActionEvent() {
		actionListener.handleActionEvent();
	}

	public void render() {
		count++;

		MBGL.enableBlend();

		// minimum radius be five pixels
		minRadius = container.getViewer().getProjection().getWorldDistance(3);

		randomPlotPassTrajs();
		renderSelectedODNodes();
		renderOD();
		renderNodeDirections();

		// renderTripInfo();
		renderLegend();
	}

	// render user selected origin and destination nodes
	protected void renderSelectedODNodes() {
		float radius = container.getViewer().getProjection()
				.getWorldDistance(10);
		if (actionListener.originNodeId != -1
				&& actionListener.originNodeId != actionListener.destinationNodeId) {
			GL11.glColor3f(1, 0, 0);
			Point2D p = container.getData().getScene()
					.getNode(actionListener.originNodeId).toPoint();
			MBGL.renderEclipse(p.getX(), p.getY(), 0, radius, radius, true);
		}

		if (actionListener.destinationNodeId != -1
				&& actionListener.originNodeId != actionListener.destinationNodeId) {
			GL11.glColor3f(0, 0, 1);
			Point2D p = container.getData().getScene()
					.getNode(actionListener.destinationNodeId).toPoint();
			MBGL.renderEclipse(p.getX(), p.getY(), 0, radius, radius, true);
		}

		if (actionListener.originNodeId != -1
				&& actionListener.originNodeId == actionListener.destinationNodeId) {
			Point2D p = container.getData().getScene()
					.getNode(actionListener.originNodeId).toPoint();

			GL11.glColor3f(1, 0, 0);
			GL11.glBegin(GL11.GL_TRIANGLE_FAN);
			GL11.glVertex3f(p.getX(), p.getY(), 0);

			int slices = 100;
			for (int i = 0; i <= 50; i++) {
				float x = (float) (p.getX() + radius
						* Math.cos(Math.PI * 2 / slices * i));
				float y = (float) (p.getY() + radius
						* Math.sin(Math.PI * 2 / slices * i));
				GL11.glVertex3f(x, y, 0);
			}

			GL11.glEnd();

			GL11.glColor3f(0, 0, 1);
			GL11.glBegin(GL11.GL_TRIANGLE_FAN);

			for (int i = 50; i <= slices; i++) {
				float x = (float) (p.getX() + radius
						* Math.cos(Math.PI * 2 / slices * i));
				float y = (float) (p.getY() + radius
						* Math.sin(Math.PI * 2 / slices * i));
				GL11.glVertex3f(x, y, 0);
			}
			GL11.glVertex3f(p.getX(), p.getY(), 0);

			GL11.glEnd();
		}
	}

	/**
	 * if origin and destination nodes overlap, draw the possible directions
	 */
	protected void renderNodeDirections() {
		if (actionListener.originNodeId == -1
				|| actionListener.originNodeId != actionListener.destinationNodeId)
			return;

		if (edgePolygonMap != null)
			for (Edge e : edgePolygonMap.keySet()) {
				if (e.equals(actionListener.selectedEdge))
					MBColorTable.assignColor(MBColorTable.GREEN, 1.0f);
				else
					MBColorTable.assignColor(MBColorTable.GREEN, 0.3f);

				Arrow.render(edgePolygonMap.get(e));
			}
	}

	// randomly plot the passing trips
	protected void randomPlotPassTrajs() {
		List<TripSegment> passTrajIds = selectLinkQuery.getPassTrips();

		float tripSize = selectLinkQuery.getPassTripSize();
		if (passTrajIds == null || passTrajIds.size() == 0)
			return;

		Random rand = new Random(1000);

		float alpha = 0.03f;
		float trajPlotSize = 5000.0f;
		if (tripSize > trajPlotSize)
			alpha = Math.min(0.5f, alpha * tripSize / trajPlotSize);

		GL11.glLineWidth(1.0f);
		for (TripSegment passTS : passTrajIds) {
			if (passTrajIds.size() > trajPlotSize
					&& rand.nextFloat() > trajPlotSize / passTrajIds.size())
				continue;

			int xSign = rand.nextBoolean() ? 1 : -1;
			int ySign = rand.nextBoolean() ? 1 : -1;

			float radius = 0.005f;
			float randomX = xSign * rand.nextFloat() * radius;
			float randomY = ySign * rand.nextFloat() * radius;

			Trip trip = passTS.getTrip();

			List<TripSegment> tripSegments = trip.getTripSegments();
			int index = tripSegments.indexOf(passTS);

			for (int k = 0; k < tripSegments.size(); k++) {
				TripSegment ts = tripSegments.get(k);
				int[] nodeIndices = ts.getNodesIndices();

				if (k != index) { // trip segments before or after selected
									// nodes
					if (k < index)
						MBColorTable.assignColor(MBColorTable.RED, alpha
								* passTS.getProbability());
					// continue;
					else if (k > index)
						MBColorTable.assignColor(MBColorTable.BLUE, alpha
								* passTS.getProbability());
					// continue;
					GL11.glBegin(GL11.GL_LINE_STRIP);
					for (int i = 0; i < nodeIndices.length; i++) {
						Node node = container.getData().getScene()
								.getNode(nodeIndices[i]);
						float x = node.toPoint().getX() + randomX / 2;
						float y = node.toPoint().getY() + randomY / 2;
						GL11.glVertex2f(x, y);
					}
					GL11.glEnd();
				} else { // trip segment passing the selected nodes
					int firstNodeIndex = -1, secondNodeIndex = -1;
					for (int i = 0; i < nodeIndices.length; i++) {
						if (nodeIndices[i] == actionListener.originNodeId)
							firstNodeIndex = i;
						if (nodeIndices[i] == actionListener.destinationNodeId)
							secondNodeIndex = i;
					}

					GL11.glBegin(GL11.GL_LINES);
					for (int i = 0; i < nodeIndices.length - 1; i++) {
						Node node = container.getData().getScene()
								.getNode(nodeIndices[i]);
						Node nextNode = container.getData().getScene()
								.getNode(nodeIndices[i + 1]);
						float x = node.toPoint().getX() + randomX / 2;
						float y = node.toPoint().getY() + randomY / 2;
						float nextX = nextNode.toPoint().getX() + randomX / 2;
						float nextY = nextNode.toPoint().getY() + randomY / 2;
						if (i + 1 <= firstNodeIndex)
							MBColorTable.assignColor(MBColorTable.RED, alpha
									* passTS.getProbability());
						else if (i >= secondNodeIndex)
							MBColorTable.assignColor(MBColorTable.BLUE, alpha
									* passTS.getProbability());
						else
							MBColorTable.assignColor(MBColorTable.BLACK, alpha
									* passTS.getProbability());
						GL11.glVertex2f(x, y);
						GL11.glVertex2f(nextX, nextY);
					}
					GL11.glEnd();
				}
			}
		}
	}

	// render origin/destination of trips passing by the selected links
	protected void renderOD() {
		GL11.glLineWidth(3.0f);

		renderOD(selectLinkQuery.getODMatrix().getOriginMatrix(), true);
		renderOD(selectLinkQuery.getODMatrix().getDestinMatrix(), false);
	}

	private void renderOD(Map<Node, Float> nodeMap, boolean origin) {
		MetroFont font = container.getFont();
		float fontSize = container.getViewer().getProjection()
				.getWorldDistance(13);
		if (nodeMap != null) {
			for (Entry<Node, Float> entry : nodeMap.entrySet()) {
				Node node = entry.getKey();
				String lineID = node.getId().substring(0, 2);
				if (lineID.equals("CG"))
					lineID = "EW";

				if (lineID.equals("BP"))
					continue;

				if (lineID.equals("EW"))
					SGMRTColorTable.assignSubwayColor(SGMRTColorTable.EW, 0.8f);
				else if (lineID.equals("NS"))
					SGMRTColorTable.assignSubwayColor(SGMRTColorTable.NS, 0.8f);
				else if (lineID.equals("CC"))
					SGMRTColorTable.assignSubwayColor(SGMRTColorTable.CC, 0.8f);
				else if (lineID.equals("NE"))
					SGMRTColorTable.assignSubwayColor(SGMRTColorTable.NE, 0.8f);
				else if (origin)
					MBColorTable.assignColor(MBColorTable.RED, 0.8f);
				else
					MBColorTable.assignColor(MBColorTable.BLUE, 0.8f);

				Float value = entry.getValue();
				float radius = (float) (Math.sqrt(value / 1000.0f) * THOURSAND_RADIUS);

				if (radius < minRadius)
					radius = minRadius;

				if (highlightNodeList != null
						&& highlightNodeList.contains(node))
					radius = highlightNode(node, radius, count);
				else
					MBGL.renderEclipse(node.getX(), node.getY(), 0, radius,
							radius, false);

				float length = font.getLength(node.getId(), fontSize);
				font.display(node.getId(), new Pen(node.getX() - length / 2,
						node.getY() - radius - fontSize * 1.1f, fontSize));
			}
		}
	}

	private float highlightNode(Node node, float initRadius, int count) {
		int loop = 30;
		int a = count / loop;
		boolean increase = (a % 2 == 0);
		int num = count - loop * a;
		float curRadius = 0;
		float maxRatio = 1.3f;
		if (increase)
			curRadius = initRadius * (1 + num * (maxRatio - 1) / loop);
		else
			curRadius = initRadius * (maxRatio - num * (maxRatio - 1) / loop);

		MBGL.renderEclipse(node.getX(), node.getY(), 0, curRadius, curRadius,
				false);

		return curRadius;
	}

	// render legend
	private void renderLegend() {
		int numberFontSize = 30;
		Projection proj = container.getViewer().getProjection();

		float fontSize = proj.getWorldDistance(numberFontSize);

		float screenX = MBViewer.getWidth() - 300f;
		float screenY = 50f;

		Point3D worldCoord = proj.getWorldCoord(new Point2D(screenX, screenY),
				0);

		Pen pen = new Pen(worldCoord.getX(), worldCoord.getY(), fontSize);

		GL11.glColor4f(1, 0, 0, 1);
		container.getFont().display("RED - Origin", pen);

		pen = new Pen(worldCoord.getX(), worldCoord.getY() - fontSize, fontSize);
		GL11.glColor4f(0, 0, 1, 1);
		container.getFont().display("BLUE - Destination", pen);
	}

	// render how many trips are selected from all trips
	protected void renderTripInfo() {
		List<TripSegment> passTrajIds = selectLinkQuery.getPassTrips();

		if (passTrajIds == null || actionListener.originNodeId == -1
				|| actionListener.destinationNodeId == -1)
			return;

		int numberFontSize = 30;
		Projection proj = container.getViewer().getProjection();

		float fontSize = proj.getWorldDistance(numberFontSize);

		float screenX = Display.getDesktopDisplayMode().getWidth() / 2;
		float screenY = 20f;

		Point3D worldCoord = proj.getWorldCoord(new Point2D(screenX, screenY),
				0);

		String str = MBTools.formatNum((int) selectLinkQuery.getPassTripSize())
				+ " trips retrieved from "
				+ MBTools.formatNum(container.getData().getScene()
						.getMRTTripSize()) + " in total";

		float adjustment = -container.getFont().getLength(str, fontSize) / 2;

		GL11.glColor4f(0, 0, 0, 1);
		container.getFont().display(
				str,
				new Pen(worldCoord.getX() + adjustment, worldCoord.getY(),
						fontSize));
	}
}
